/* Declarations for the stream type.

   This file is part of khipu.

   khipu is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

#ifndef __KP_STREAM__
#define __KP_STREAM__   1

#include "interp.hpp"
#include "initop.hpp"
#include "llio.hpp"

KP_DECLS_BEGIN

struct schar
{
  unsigned char buf[4];
  int len;
  uint32_t uc;
};

struct spos
{
  int64_t offset;
  object state;

  spos (int64_t off = 0, object st = UNBOUND) : offset (off), state (st) {}

  result<object> encode (interpreter *interp);

  static spos decode (object obj);
};

// Stream flags.
enum
{
  STRM_BUILTIN = 1 << 0,
  STRM_READ    = 1 << 1,
  STRM_WRITE   = 1 << 2,
  STRM_RDWR    = STRM_READ | STRM_WRITE,
  STRM_APP     = 1 << 3,
  STRM_TEXT    = 1 << 4,
  STRM_BIN     = 1 << 5,
  STRM_EOS     = 1 << 6,
  STRM_ERR     = 1 << 7,
  STRM_LASTR   = 1 << 8,
  STRM_LASTW   = 1 << 9,
  STRM_UTF8    = 1 << 10,
  STRM_OFFSET  = 1 << 11,
  STRM_CLOSED  = 1 << 12,
  STRM_BID     = 1 << 13,
  STRM_NOLOCK  = 1 << 14
};

struct stream : public finobj
{
  static const int code = typecode::STREAM;

  struct buffer
    {
      char *start;
      char *curr;
      char *end;

      void init (void *s, unsigned int sz)
        {
          this->start = this->curr = (char *)s;
          this->end = this->start + sz;
        }

      int left () const
        {
          return ((int)(this->end - this->curr));
        }

      int used () const
        {
          return ((int)(this->curr - this->start));
        }

      void set (void *ptr)
        {
          this->curr = (char *)ptr;
        }

      void bump (int n)
        {
          this->curr += n;
        }
    };

  struct xops
    {
      result<int64_t> (*read) (interpreter *, stream&, void *, uint64_t);
      result<int64_t> (*write) (interpreter *, stream&, const void *, uint64_t);
      result<bool> (*seek) (interpreter *, stream&, spos&, int);
      bool (*close) (interpreter *, stream&);
    };

  buffer rdbuf;
  buffer wrbuf;
  object bvec;
  const xops *ops;
  void *cookie;
  spos pos;
  object ilock;
  object extra;
  uint32_t io_flags;

  static result<stream*> make (interpreter *interp, int mode,
                               unsigned int bufsiz, const xops *ops,
                               void *cookie);

  int bufsize () const;

  void set_lastr ()
    {
      this->io_flags = (this->io_flags & ~STRM_LASTW) | STRM_LASTR;
    }

  void set_lastw ()
    {
      this->io_flags = (this->io_flags & ~STRM_LASTR) | STRM_LASTW;
    }

  result<int64_t> xread (interpreter *interp, void *outp, uint64_t sz)
    {
      return (this->ops->read (interp, *this, outp, sz));
    }

  result<int64_t> xwrite (interpreter *interp, const void *inp, uint64_t sz)
    {
      return (this->ops->write (interp, *this, inp, sz));
    }

  result<bool> xseek (interpreter *, const spos& pos, int whence);

  bool xclose (interpreter *interp)
    {
      return (this->ops->close (interp, *this));
    }

  bool need_wflush () const
    {
      return ((this->io_flags & (STRM_LASTW | STRM_BID)) == STRM_LASTW);
    }

  bool need_rflush () const
    {
      return ((this->io_flags & (STRM_LASTR | STRM_BID)) == STRM_LASTR);
    }

  void add_pos (int64_t offset)
    {
      this->pos.offset += offset;
    }

  void discard ()
    {
      this->rdbuf.curr = this->rdbuf.end;
    }

  // Write NB bytes from SRC to the stream.
  result<int64_t> write (interpreter *interp, const void *src, uint64_t nb);

  template <typename T>
  result<int64_t> write (interpreter *interp, const T *outp)
    {
      return (this->write (interp, outp, sizeof (T)));
    }

  // Write a single byte into the stream.
  result<int> putb (interpreter *interp, int byte);

  // Write N times BYTE into the stream.
  result<int> nputb (interpreter *interp, uint32_t n, int byte);

  // Write the unicode character CH to the stream.
  result<int> putuc (interpreter *interp, uint32_t ch);

  // Write N times the unicode character CH to the stream.
  result<int> putnuc (interpreter *interp, unsigned int n, uint32_t ch);

  result<int> wbase (interpreter *interp, int base);

  // Read a single byte from the stream without advancing the stream pointer.
  result<int> peekb (interpreter *interp);

  // Read a single byte from the stream.
  result<int> getb (interpreter *interp);

  // Read at most NB bytes from the stream into DST.
  result<int64_t> read (interpreter *interp, void *dst, uint64_t nb);

  template <typename T>
  result<int64_t> read (interpreter *interp, T *outp)
    {
      return (this->read (interp, outp, sizeof (T)));
    }

  template <typename T>
  result<bool> sread (interpreter *interp, T *outp)
    {
      auto rv = KP_TRY (this->read (interp, outp));
      return ((size_t)rv == sizeof (T));
    }

  // Read a unicode character from the stream into CH.
  result<bool> sgetc (interpreter *interp, schar& ch);

  // Read a unicode character from the stream.
  result<uint32_t> getuc (interpreter *interp);

  // Unget LEN characters from S from the stream.
  bool ungetuc (const unsigned char *s, int len);

  // Move the stream possition according to OFF and WHENCE.
  result<bool> seek (interpreter *interp, object off, int whence);

  // Get current stream position.
  result<object> tell (interpreter *interp)
    {
      return (this->pos.encode (interp));
    }

  // Flush the contents of the stream.
  result<bool> flush (interpreter *interp);

  // Close the stream.
  result<bool> close (interpreter *interp);

  // Test if we are at the end of the stream.
  bool eos_p () const
    {
      return ((this->io_flags & STRM_EOS) != 0);
    }

  // Test for errors on the stream.
  bool err_p () const
    {
      return ((this->io_flags & STRM_ERR) != 0);
    }

  // Internal definitions.
  void reset_buff ()
    {
      this->io_flags &= ~(STRM_LASTR | STRM_LASTW);
      this->rdbuf.set (this->rdbuf.end);
      this->wrbuf.set (this->wrbuf.start);
    }

  result<bool> write_full (interpreter *interp, const void *src,
                           uint64_t bytes);

  result<bool> wflush (interpreter *interp)
    {
      bool ret = KP_TRY (this->write_full (interp, this->wrbuf.start,
                                           this->wrbuf.used ()));
      if (ret)
        this->reset_buff ();

      return (ret);
    }

  result<bool> rflush (interpreter *interp);
  result<bool> refill (interpreter *interp);

  result<void> lock (interpreter *interp);
  bool trylock (interpreter *interp);
  void unlock (interpreter *inter);
};

inline stream* as_stream (object obj)
{
  return ((stream *)unmask (obj));
}

inline bool stream_p (object obj)
{
  return (itype (obj) == typecode::STREAM);
}

// Basic buffer size.
const int STRM_BUFSIZ = 512;

// Unicode EOF value.
const uint32_t UEOF = 0xffffffff;

struct stream_guard
{
  interpreter *interp;
  stream *strmp;

  stream_guard (interpreter *ip, stream *s) : interp (ip), strmp (s)
    {
    }

  stream*& operator* ()
    {
      return (this->strmp);
    }

  stream* operator* () const
    {
      return (this->strmp);
    }

  object as_obj () const
    {
      return (strmp->as_obj ());
    }

  ~stream_guard ()
    {
      evh_safeguard { this->interp };
      if (this->strmp)
        deref (this->strmp->close (this->interp));
    }
};

struct io_info
{
  static const uint32_t FLG_RAW = 1u << 0;
  static const uint32_t FLG_I18N = 1u << 1;
  static const uint32_t FLG_ALT = 1u << 2;
  static const uint32_t FLG_SPACE = 1u << 3;
  static const uint32_t FLG_LJUST = 1u << 4;
  static const uint32_t FLG_ZERO = 1u << 5;
  static const uint32_t FLG_SIGN = 1u << 6;
  static const uint32_t FLG_EXPO = 1u << 7;
  static const uint32_t FLG_FIXED = 1u << 8;
  static const uint32_t FLG_SAFE = 1u << 9;

  static const int DFL_RADIX = 10;
  static const int DFL_PREC = 6;
  static const int DFL_WIDTH = -1;
  static const uint32_t DFL_FLAGS = FLG_RAW;

  int radix;
  int prec;
  int width;
  uint32_t flags;

  io_info (uint32_t flg = 0) : radix (DFL_RADIX), prec (DFL_PREC),
      width (DFL_WIDTH), flags (DFL_FLAGS | flg)
    {
    }
};

// Basic streams.
KP_EXPORT object in_stream;
KP_EXPORT object out_stream;
KP_EXPORT object err_stream;

KP_EXPORT const object EOS;

// File streams.

KP_EXPORT result<stream*> fstream_open (interpreter *interp,
                                        const char *path, const char *mode);

KP_EXPORT result<bool> fstream_truncate (interpreter *interp,
                                         stream *strm, uint64_t nsize);

KP_EXPORT result<bool> fstream_fstat (interpreter *interp,
                                      stream *strm, fhandle_stat& out);

// Init OP for streams.
KP_EXPORT init_op init_streams;

KP_DECLS_END

#endif
